package asf.core
{
	import asf.debug.DebugPanel;
	import asf.events.ApplicationEvent;
	import asf.events.ApplicationLoadProgressEvent;
	import asf.interfaces.ISectionManagement;
	import asf.log.LogLevel;
	import asf.log.Logger;
	import asf.sections.Section;
	
	import br.com.stimuli.loading.BulkProgressEvent;
	
	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	
	[Event( type="asf.events.ApplicationEvent", name="configFileLoaded" )]
	[Event( type="asf.events.ApplicationEvent", name="afterConfigFileLoaded" )]
	[Event( type="asf.events.ApplicationEvent", name="configFileIOError" )]
	
	/**
	 * This is the main class of ASF Framework.
	 * Multiple applications are supported. You just must give an ID to each Application instance.
	 * 
	 * Usage example:
	 * 
	 * app = new Application( container, root.loaderInfo, Globals.APP_ID );
			
		app.addEventListener( ApplicationEvent.CONFIG_FILE_LOADED, appOnConfigLoaded );
		app.addEventListener( ApplicationEvent.AFTER_CONFIG_FILE_LOADED, appAfterConfigLoaded );
		
		app.addEventListener( ApplicationLoadProgressEvent.DEPENDENCIES_LOAD_COMPLETE, appOnComplete );
		app.addEventListener( ApplicationLoadProgressEvent.DEPENDENCIES_LOAD_START, appOnLoadStart );
		
		app.parameters.defaults.basepath = "../";
		app.parameters.defaults.config = "{basepath}xml/application.xml";
		
		app.load( app.parameters.config ); 
	 * 
	 * 
	 */
	public class Application extends BaseLoadable
	{
		private static var instances:Object;
		private static var instancesCounter:uint = 0;
		
		private static var _id:String;
		
		private var fileLoader:URLLoader;
		
		private var configXML:XML;
		private var _loaderInfo:LoaderInfo;
		
		private var _parameters:ApplicationParameters;
		private var _navigation:Navigation;
		private var _layers:Layers;
		
		private var _container:Sprite;
		private var _popsContainer:Sprite;
		
		private var _defaultSectionManager:ISectionManagement;
		
		private var debugPanel:DebugPanel;
		private var _popups:PopupsManager;
		
		private var startSections:Array;
		private var logger:Logger;
		
		/**
		 * Constructor
		 * 
		 * @param p_container The container for the application. All layers and sections will be added in it.
		 * @param p_loaderInfo LoaderInfo object containing all flashvars params
		 * @param p_id Optional param to identify your application. You can use the 'getByID' method to get application instances. 
		 * 
		 */
		public function Application( p_container:Sprite, p_loaderInfo:LoaderInfo, p_id:String = "" )
		{
			super( this );
			
			instancesCounter ++;
			_container = p_container;
			
			_id = ( p_id != "" )? p_id: "Application_" + instancesCounter;
			_loaderInfo = p_loaderInfo;
			
			logger = new Logger( );
			
			keepInstance( );
			init( );
		}
		
		public function setLogLevel( level:uint ):void
		{
			logger.level = level;
		}
		
		public function getLogLevel( ):uint
		{
			return logger.level;
		}
		
		public function log( message:* = null, level:int = -1 ):void
		{	
			logger.log( message, level );
		}
		
		private function _log( message:* = null, level:int = - 1 ):void
		{
			logger.log( message, level, 0 );
		}
		
		public function get loaderInfo( ):LoaderInfo
		{
			return _loaderInfo;
		}
		
		/**
		 * Adds debug panel to stage.
		 * Each application will have it's own debug panel.
		 */
		private function addDebugPanel( evt:Event = null ):void
		{
			_container.removeEventListener( Event.ADDED_TO_STAGE, addDebugPanel );
			
			if( parameters.debug == "true" || parameters.debugpanel == "true" )
			{
				debugPanel = new DebugPanel( this );
				_container.stage.addChild( debugPanel );
			}
		}
		
		/**
		 * Application's main container
		 * 
		 * @return the container
		 */
		public function get container( ):Sprite
		{
			return _container;
		}
		
		/**
		 * Popups container
		 * 
		 * @return the container
		 */
		public function get popsContainer( ):Sprite
		{
			return _popsContainer;
		}
		
		/**
		 * Get an Application
		 * 
		 * @param id The identifier to get the Application
		 * @return An instance with id equals the passed id
		 */
		public static function getByID( id:String ):Application
		{
			if( instances )
			{
				return instances[ id ];
			}
			
			return null;
		}
		
		/**
		 * Initialize the application
		 */
		private function init( ):void
		{
			_log( "", LogLevel.INFO_3 );
			
			configXML = new XML( "<empty_app />" );
			
			_parameters = new ApplicationParameters( configXML, loaderInfo, this );
			fileLoader = new URLLoader( );
			
			fileLoader.addEventListener( Event.COMPLETE, onConfigFileLoaded );
			fileLoader.addEventListener( IOErrorEvent.IO_ERROR, onConfigFileIOError );
			
			if( container.parent )
			{
				createPopupsLayer( );
			}
			else
			{
				container.addEventListener( Event.ADDED_TO_STAGE, createPopupsLayer );
			}
		}
		
		/**
		 * Create popups layer
		 */
		private function createPopupsLayer( evt:Event = null ):void
		{
			_popsContainer = new Sprite( );
			container.parent.addChild( _popsContainer );
		}
		
		/**
		 * Store the instance to get later with 'getByID' method
		 */
		private function keepInstance( ):void
		{
			if( instances == null ) instances = new Object( );
			instances[ _id ] = this;
		}
		
		/**
		 * @return Application id
		 */
		public function getId( ):String
		{
			return _id;
		}
		
		/**
		 * 
		 */
		public function load( file:* ):void
		{
			_log( file, LogLevel.INFO_3 );
			
			if( file is String )
			{
				file = getParsedValueOf( file );
				fileLoader.load( new URLRequest( file ) );
			}
			else if( file is XML )
			{
				log( "XML" );
				this.configXML = file as XML;
				this.onConfigFileLoaded( null );
			}
		}
		
		private function onConfigFileLoaded( evt:Event ):void
		{
			if( configXML != null && configXML.name( ) == "empty_app" )
			{
				this.configXML = new XML( fileLoader.data );
			}
			
			this.rawXML = this.configXML;
			
			_log( "", LogLevel.INFO_3 );
			
			_parameters.setData( configXML );
			
			this.dispatchEvent( new ApplicationEvent( ApplicationEvent.CONFIG_FILE_LOADED ) );
			
			if( this.parameters.dictionaryfile != "" )
			{
				configXML.dependencies.file += new XML( this.formatDependencyNode( "$_dict_0", parameters.dictionaryfile, "xml" ) );
			}
			
			if( _container.stage )
			{
				addDebugPanel( );
			}
			else
			{
				_container.addEventListener( Event.ADDED_TO_STAGE, addDebugPanel );
			}
			
			addStartSections( );
			
			this.initDependencies( configXML.dependencies );
			
			loadStartSections( );
			
			this.dispatchEvent( new ApplicationEvent( ApplicationEvent.AFTER_CONFIG_FILE_LOADED ) );
		}
		
		public function getRawXML( ):XML
		{
			return this.rawXML.copy( );
		}
		
		protected override function dependenciesOnProgress( evt:BulkProgressEvent ):void
		{
			super.dependenciesOnProgress( getDependenciesEvent( evt.type, evt.bubbles, evt.cancelable ) );
		}
		
		private function getDependenciesEvent( type:String, bubbles:Boolean, cancelable:Boolean ):BulkProgressEvent
		{
			var itemsTotal:uint = this.dependencies.itemsTotal;
			var itemsLoaded:uint = this.dependencies.itemsLoaded;
			var bytesLoaded:uint = this.dependencies.bytesLoaded;
			var bytesTotal:uint = this.dependencies.bytesTotal;
			var w:Number = this.dependencies.weightPercent;
			var evt:BulkProgressEvent;
			
			var section:Section;
			
			for each( section in startSections )
			{
				itemsTotal += section.dependencies.itemsTotal;
				itemsLoaded += section.dependencies.itemsLoaded;
				
				bytesLoaded += section.dependencies.bytesLoaded;
				bytesTotal += section.dependencies.bytesTotal;
				
				w += section.dependencies.weightPercent;
			}
			
			if( startSections.length > 0 )
			{
				w /= startSections.length;
			}
			
			evt = new BulkProgressEvent( type, bubbles, cancelable );
			evt.setInfo( bytesLoaded, bytesTotal, bytesTotal, itemsLoaded, itemsTotal, w );
			
			return evt;
		}
		
		protected override function dependenciesOnComplete(evt:BulkProgressEvent):void
		{
			var section:Section;
			var allLoaded:Boolean = true;
			
			for each( section in startSections )
			{
				if( !section.isLoaded ) allLoaded = false;
				else
				{
					section.removeEventListener( ApplicationLoadProgressEvent.DEPENDENCIES_LOAD_COMPLETE, dependenciesOnComplete );
					section.removeEventListener( ApplicationLoadProgressEvent.DEPENDENCIES_LOAD_PROGRESS, dependenciesOnProgress );
				}
			}
			
			if( allLoaded && this.dependencies.isFinished )
			{
				super.dependenciesOnComplete( getDependenciesEvent( evt.type, evt.bubbles, evt.cancelable ) );
				startBackgroundSections( );
			}
		}
		
		private function startBackgroundSections( ):void
		{
			var section:Section;
			var sections:Array = this.navigation.sections;
			
			for each( section in sections )
			{
				if( ! section.isLoaded )
				{
					section.startBackgroundLoading( );
				}
			}
		}
		
		private function addStartSections( ):void
		{
			var section:Section;
			var sections:Array = navigation.sections;
			
			startSections = new Array( );
			
			for each( section in sections )
			{
				if( section.loadAtStart )
				{
					startSections.push( section );
				}
			}
		}
		
		private function loadStartSections( ):void
		{
			var section:Section;
			
			for each( section in startSections )
			{
				section.addEventListener( ApplicationLoadProgressEvent.DEPENDENCIES_LOAD_PROGRESS, this.dependenciesOnProgress );
				section.addEventListener( ApplicationLoadProgressEvent.DEPENDENCIES_LOAD_COMPLETE, this.dependenciesOnComplete );
				
				section.load( );
			}
		}
		
		public override function pauseLoading( ):void
		{
			if( startSections && startSections.length > 0 )
			{
				var section:Section;
				
				for each( section in startSections )
				{
					section.pauseLoading( );
				}
			}
			
			super.pauseLoading( );
		}
		
		public override function resumeLoading( ):void
		{
			if( startSections && startSections.length > 0 )
			{
				var section:Section;
				
				for each( section in startSections )
				{
					section.resumeLoading( );
				}
			}
			
			super.resumeLoading( );
		}
		
		private function onConfigFileIOError( evt:IOErrorEvent ):void
		{
			log( "Config file could not be loaded", LogLevel.ERROR_1 );
			this.dispatchEvent( new ApplicationEvent( ApplicationEvent.CONFIG_FILE_IO_ERROR ) );
		}
		
		public function get parameters( ):ApplicationParameters
		{
			return _parameters;
		}
		
		public function get navigation( ):Navigation
		{
			if( _navigation == null )
			{
				_navigation = new Navigation( configXML, this );
				
				if( this.parameters.useContextMenu == "true" )
				{
					layers.setContextMenu( _navigation.getContextMenu( ) );
				}
			}
			
			return _navigation;
		}
		
		public function get popups( ):PopupsManager
		{
			if( _popups == null )
			{
				_popups = new PopupsManager( configXML.popups, this );
			}
			
			return _popups;
		}
		
		public function get layers( ):Layers
		{
			if( _layers == null )
			{
				_layers = new Layers( XML( configXML.layers ), this );
			}
			return _layers;
		}
		
		public function getParsedValueOf( raw:String ):String
		{
			return ApplicationParser.parse( raw, configXML, this );
		} 
		
		public override function dispose( unloadDependencies:Boolean = true ):void
		{
			super.dispose( );
			navigation.dispose( );
		}
	}
}